from    os          import stat
from    json        import loads, dumps
# ============================================================================
# ===( Class Response  )======================================================
# ============================================================================

class _response :

    # ------------------------------------------------------------------------

    def __init__(self, client) :
        self._client = client

    # ------------------------------------------------------------------------

    def _write(self, data, strEncoding='ISO-8859-1') :
        if data :
            if type(data) == str :
                data = data.encode(strEncoding)
            data = memoryview(data)
            while data :
                n = self._client._socketfile.write(data)
                if n is None :
                    return False
                data = data[n:]
            return True
        return False

    # ------------------------------------------------------------------------

    def _writeFirstLine(self, code) :
        reason = self._responseCodes.get(code, ('Unknown reason', ))[0]
        return self._write("HTTP/1.1 %s %s\r\n" % (code, reason))

    # ------------------------------------------------------------------------

    def _writeHeader(self, name, value) :
        return self._write("%s: %s\r\n" % (name, value))

    # ------------------------------------------------------------------------

    def _writeContentTypeHeader(self, contentType, charset=None) :
        if contentType :
            ct = contentType \
                + (("; charset=%s" % charset) if charset else "")
        else :
            ct = "application/octet-stream"
        self._writeHeader("Content-Type", ct)

    # ------------------------------------------------------------------------

    def _writeServerHeader(self) :
        self._writeHeader("Server", "MicroWebSrv by JC`zic")

    # ------------------------------------------------------------------------

    def _writeEndHeader(self) :
        return self._write("\r\n")

    # ------------------------------------------------------------------------

    def _writeBeforeContent(self, code, headers, contentType, contentCharset, contentLength) :
        self._writeFirstLine(code)
        if isinstance(headers, dict) :
            for header in headers :
                self._writeHeader(header, headers[header])
        if contentLength > 0 :
            self._writeContentTypeHeader(contentType, contentCharset)
            self._writeHeader("Content-Length", contentLength)
        self._writeServerHeader()
        self._writeHeader("Connection", "close")
        self._writeEndHeader()

    # ------------------------------------------------------------------------

    def WriteSwitchProto(self, upgrade, headers=None) :
        self._writeFirstLine(101)
        self._writeHeader("Connection", "Upgrade")
        self._writeHeader("Upgrade",    upgrade)
        if isinstance(headers, dict) :
            for header in headers :
                self._writeHeader(header, headers[header])
        self._writeServerHeader()
        self._writeEndHeader()
        if self._client._socketfile is not self._client._socket :
            self._client._socketfile.flush()   # CPython needs flush to continue protocol

    # ------------------------------------------------------------------------

    def WriteResponse(self, code, headers, contentType, contentCharset, content) :
        try :
            if content :
                if type(content) == str :
                    content = content.encode(contentCharset)
                contentLength = len(content)
            else :
                contentLength = 0
            self._writeBeforeContent(code, headers, contentType, contentCharset, contentLength)
            if content :
                return self._write(content)
            return True
        except :
            return False

    # ------------------------------------------------------------------------

    def WriteResponsePyHTMLFile(self, filepath, headers=None, vars=None) :
        if 'MicroWebTemplate' in globals() :
            with open(filepath, 'r') as file :
                code = file.read()
            mWebTmpl = MicroWebTemplate(code, escapeStrFunc=MicroWebSrv.HTMLEscape, filepath=filepath)
            try :
                tmplResult = mWebTmpl.Execute(None, vars)
                return self.WriteResponse(200, headers, "text/html", "UTF-8", tmplResult)
            except Exception as ex :
                return self.WriteResponse( 500,
                                            None,
                                            "text/html",
                                            "UTF-8",
                                            self._execErrCtnTmpl % {
                                                'module'  : 'PyHTML',
                                                'message' : str(ex)
                                            } )
        return self.WriteResponseNotImplemented()

    # ------------------------------------------------------------------------

    def WriteResponseFile(self, filepath, contentType=None, headers=None) :
        try :
            size = stat(filepath)[6]
            if size > 0 :
                with open(filepath, 'rb') as file :
                    self._writeBeforeContent(200, headers, contentType, None, size)
                    try :
                        buf = bytearray(1024)
                        while size > 0 :
                            x = file.readinto(buf)
                            if x < len(buf) :
                                buf = memoryview(buf)[:x]
                            if not self._write(buf) :
                                return False
                            size -= x
                        return True
                    except :
                        self.WriteResponseInternalServerError()
                        return False
        except :
            pass
        self.WriteResponseNotFound()
        return False

    # ------------------------------------------------------------------------

    def WriteResponseFileAttachment(self, filepath, attachmentName, headers=None) :
        if not isinstance(headers, dict) :
            headers = { }
        headers["Content-Disposition"] = "attachment; filename=\"%s\"" % attachmentName
        return self.WriteResponseFile(filepath, None, headers)

    # ------------------------------------------------------------------------

    def WriteResponseOk(self, headers=None, contentType=None, contentCharset=None, content=None) :
        return self.WriteResponse(200, headers, contentType, contentCharset, content)

    # ------------------------------------------------------------------------

    def WriteResponseJSONOk(self, obj=None, headers=None) :
        return self.WriteResponse(200, headers, "application/json", "UTF-8", dumps(obj))

    # ------------------------------------------------------------------------

    def WriteResponseRedirect(self, location) :
        headers = { "Location" : location }
        return self.WriteResponse(302, headers, None, None, None)

    # ------------------------------------------------------------------------

    def WriteResponseError(self, code) :
        responseCode = self._responseCodes.get(code, ('Unknown reason', ''))
        return self.WriteResponse( code,
                                    None,
                                    "text/html",
                                    "UTF-8",
                                    self._errCtnTmpl % {
                                        'code'    : code,
                                        'reason'  : responseCode[0],
                                        'message' : responseCode[1]
                                    } )

    # ------------------------------------------------------------------------

    def WriteResponseJSONError(self, code, obj=None) :
        return self.WriteResponse( code,
                                    None,
                                    "application/json",
                                    "UTF-8",
                                    dumps(obj if obj else { }) )

    # ------------------------------------------------------------------------

    def WriteResponseNotModified(self) :
        return self.WriteResponseError(304)

    # ------------------------------------------------------------------------

    def WriteResponseBadRequest(self) :
        return self.WriteResponseError(400)

    # ------------------------------------------------------------------------

    def WriteResponseForbidden(self) :
        return self.WriteResponseError(403)

    # ------------------------------------------------------------------------

    def WriteResponseNotFound(self) :
        if self._client._microWebSrv._notFoundUrl :
            self.WriteResponseRedirect(self._client._microWebSrv._notFoundUrl)
        else :
            return self.WriteResponseError(404)

    # ------------------------------------------------------------------------

    def WriteResponseMethodNotAllowed(self) :
        return self.WriteResponseError(405)

    # ------------------------------------------------------------------------

    def WriteResponseInternalServerError(self) :
        return self.WriteResponseError(500)

    # ------------------------------------------------------------------------

    def WriteResponseNotImplemented(self) :
        return self.WriteResponseError(501)

    # ------------------------------------------------------------------------

    def FlashMessage(self, messageText, messageStyle='') :
        if 'MicroWebTemplate' in globals() :
            MicroWebTemplate.MESSAGE_TEXT = messageText
            MicroWebTemplate.MESSAGE_STYLE = messageStyle

    # ------------------------------------------------------------------------

    _errCtnTmpl = """\
    <html>
        <head>
            <title>Error</title>
        </head>
        <body>
            <h1>%(code)d %(reason)s</h1>
            %(message)s
        </body>
    </html>
    """

    # ------------------------------------------------------------------------

    _execErrCtnTmpl = """\
    <html>
        <head>
            <title>Page execution error</title>
        </head>
        <body>
            <h1>%(module)s page execution error</h1>
            %(message)s
        </body>
    </html>
    """

    # ------------------------------------------------------------------------

    _responseCodes = {
        100: ('Continue', 'Request received, please continue'),
        101: ('Switching Protocols',
                'Switching to new protocol; obey Upgrade header'),

        200: ('OK', 'Request fulfilled, document follows'),
        201: ('Created', 'Document created, URL follows'),
        202: ('Accepted',
                'Request accepted, processing continues off-line'),
        203: ('Non-Authoritative Information', 'Request fulfilled from cache'),
        204: ('No Content', 'Request fulfilled, nothing follows'),
        205: ('Reset Content', 'Clear input form for further input.'),
        206: ('Partial Content', 'Partial content follows.'),

        300: ('Multiple Choices',
                'Object has several resources -- see URI list'),
        301: ('Moved Permanently', 'Object moved permanently -- see URI list'),
        302: ('Found', 'Object moved temporarily -- see URI list'),
        303: ('See Other', 'Object moved -- see Method and URL list'),
        304: ('Not Modified',
                'Document has not changed since given time'),
        305: ('Use Proxy',
                'You must use proxy specified in Location to access this '
                'resource.'),
        307: ('Temporary Redirect',
                'Object moved temporarily -- see URI list'),

        400: ('Bad Request',
                'Bad request syntax or unsupported method'),
        401: ('Unauthorized',
                'No permission -- see authorization schemes'),
        402: ('Payment Required',
                'No payment -- see charging schemes'),
        403: ('Forbidden',
                'Request forbidden -- authorization will not help'),
        404: ('Not Found', 'Nothing matches the given URI'),
        405: ('Method Not Allowed',
                'Specified method is invalid for this resource.'),
        406: ('Not Acceptable', 'URI not available in preferred format.'),
        407: ('Proxy Authentication Required', 'You must authenticate with '
                'this proxy before proceeding.'),
        408: ('Request Timeout', 'Request timed out; try again later.'),
        409: ('Conflict', 'Request conflict.'),
        410: ('Gone',
                'URI no longer exists and has been permanently removed.'),
        411: ('Length Required', 'Client must specify Content-Length.'),
        412: ('Precondition Failed', 'Precondition in headers is false.'),
        413: ('Request Entity Too Large', 'Entity is too large.'),
        414: ('Request-URI Too Long', 'URI is too long.'),
        415: ('Unsupported Media Type', 'Entity body in unsupported format.'),
        416: ('Requested Range Not Satisfiable',
                'Cannot satisfy request range.'),
        417: ('Expectation Failed',
                'Expect condition could not be satisfied.'),

        500: ('Internal Server Error', 'Server got itself in trouble'),
        501: ('Not Implemented',
                'Server does not support this operation'),
        502: ('Bad Gateway', 'Invalid responses from another server/proxy.'),
        503: ('Service Unavailable',
                'The server cannot process the request due to a high load'),
        504: ('Gateway Timeout',
                'The gateway server did not receive a timely response'),
        505: ('HTTP Version Not Supported', 'Cannot fulfill request.'),
    }

# ============================================================================
# ============================================================================
# ============================================================================

